跳到主要内容

Express框架学习

概述

Express框架是什么

官方中文文档 民间中文文档

Express 是一个基于 Node 平台的 web应用开发框架,它提供了一系列的强大特性

Express 框架核心特性:

  • 可以设置中间件来响应 HTTP 请求。
  • 定义了路由表用于执行不同的 HTTP 请求动作。
  • 可以通过向模板传递参数来动态渲染 HTML 页面。

安装

npm install express --save

基本的使用例

// 引入模块
const express = require('express')

// 创建网站服务器
const app = express();

// 如果是 / 表示这个是根项目(和javaWeb 一样)
app.get('/',(req,res)=>{
// 响应方法是 send() 能够自动把 响应类型、类型编码、http状态码 等信息写到响应头里
res.send('Hello Express !')
})

// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');

自动更新服务

nodemon 可以随时监听文件的变更,自动重启服务,总之就是 node 服务器开发用的工具

# 安装依赖
npm install -g nodemon

使用时只需使用它来启动服务就行了

nodemon index.js

当修改文件之后再保存会自动重启服务器,省的每次还需要手动重启

中间件

中间件基本使用

中间件就是一堆方法,可以接收客户端发来的请求、可以对请求做出响应,也可以将请求继续交给下一个中间件继续处理

中间件由两部分构成,中间方法以及请求处理函数

其中

  • 中间方法由Express提供,负责拦截请求(就是get、post这些方法)
  • 请求处理函数由开发人员提供,负责处理请求(就是get、post的回调函数)
app.get('请求路径','处理函数')

请求从上到下依次匹配中间件,一旦匹配成功,终止匹配

但是可以调用next方法将请求的控制权交给下一个中间件,直到遇到结束请求 send() 的中间件

app.get('/',(req,res,next)=>{
// 注意!! 如果要传递给下一个中间件就不能在这里 使用 res.send()
// 添加一个属性,可以传递给下面那个中间件
req.name = '张三'
next()
})

app.get('/',(req,res)=>{
res.send(req.name + 'this is 2')
})

use 中间件的用法

app.use 匹配所有的请求方式,可以直接传入请求处理函数,代表接收所有的请求

注意:虽然这个use能接收到所有的请求,但是因为中间件是有顺序的,所以最好放在上面

app.use((req,res,next)=>{
// 这个 req.url 能接收到 所有发向这个端口的请求,包括没有组测中间件的请求
console.log(req.url);
console.log('这里是use方法');
next()
})

除了上面这种不写请求路径的写法,还可以指定请求路径,使所有访问这个请求路径的请求(get、post...)都经过它,但是也要注意顺序问题

app.use('/request',  (req,res,next)=>{
console.log('这里是use方法');
next()
})

最后别忘了use中间件最后要加上一个next 否则会一直卡在这个中间这里

use 调用函数的用法

可以在use里传递一个函数来执行

const express = require('express');
// 创建网站服务器
const app = express();

app.use(fn({ a: 1 }));

// use 可以传递一个函数进行调用
function fn(obj) {
// 要返回一个函数
return function (req, res, next) {
if (obj.a == 1) {
console.log(req.url);
} else {
console.log(req.method);
}

next();
}
}

app.get('/', (req, res) => {
res.send('use 方法执行成功');
});

console.log('启动服务器');
app.listen(3000);

实际上就相当于直接写在里面

等价于上面的代码

const express = require('express');
// 创建网站服务器
const app = express();

app.use((req, res, next) => {
let obj = { a: 1 };

if (obj.a == 1) {
console.log(req.url);
} else {
console.log(req.method);
}

next();
})

app.get('/', (req, res) => {
res.send('use 方法执行成功');
})

console.log('启动服务器');
app.listen(3000);

中间件的应用

  1. 路由保护,客户端在访问需要登陆的页面时,可以先使用中间件判断用户登陆状态,用户如果未登录,则拦截请求,直接响应,禁止用户进入需要登陆的页面
app.use((req,res,next)=>{
let isLogin = false
if (isLogin) {
next()
}else{
res.send('未登录,请先登陆')
}
})

app.get('/',(req,res)=>{
res.send('登陆成功,可以直接访问这个页面')
})

  1. 网站维护公告,在所有路由的最上面定义接收所有请求的中间件,直接为客户端做出响应,网站正在维护中

  2. 自定义 404 页面(放在最下面就行了,表示前面的中间件都没有匹配成功)

app.use((req,res,next)=>{
// 为客户端响应定义404状态码以及提示信息
res.status(404).send('这是一个404页面')
})

错误处理中间件

在程序执行过程中,不可避免的会出现一些无法预料的错误,比如文件读取失败,数据库连接失败。错误处理中间件是一个 集中处理错误的地方

app.get('/',(req,res)=>{
throw new Error('程序发生了未知错误')
})

// 错误处理中间件函数的定义方式与其他中间件函数基本相同,差别在于错误处理函数有四个自变量而不是三个:(err, req, res, next)
app.use((err,req,res,next)=>{
res.status(500).send(err.message + "--服务器发生未知错误")
})

但是注意! 错误处理中间件 只能捕获到同步代码的错误,如果是异步代码发生了错误是无法捕获到的,这时需要使用 next() 方法 手动去触发错误处理中间件

// 这个fs是文件读取模块
const fs = require('fs')

app.get('/',(req,res,next)=>{
fs.readFile("/file-does-not-exist",(err,data)=>{
if(err){
next(err)
}
})
})

捕获错误

app.get('/',async (req,res,next)=>{
try {
await User.find({name: '张三'})
}catch(ex){
next(ex)
}
})

上面可以通过 next 传递给错误处理中间件

all和use的区别

all 执行完整匹配,use 只匹配前缀

app.use('/a', (req, res, next) => {
console.log('app.use')
next()
})

app.all('/a', (req, res, next) =>{
console.log('app.all')
next()
})

访问 /a use 和 all 都会被调用;访问 /a/b 只有 use 被调用

Express 请求处理

路由模块

就是将二级路由放到其他文件管理,一级路由主文件管理

然后在主文件里把二级路由和一级路由绑定起来

普通的使用二级路由

const express = require('express');
// 创建网站服务器
const app = express();

// 创建路由对象 const home = express.Router();

// 将路由和请求路径进行匹配 app.use('/home', home);

// 在home路由下继续创建路由(二级路由) home.get('/index', (req, res) => { res.send('欢迎页'); });

console.log('启动服务器'); app.listen(3000)


> 构建路由模块

模块一
```js
// home.js
const home = express.Router();

home.get('/index', (req, res) => {
res.send('欢迎页');
});

module.exports = home;

模块二

// admin.js
const admin = express.Router();

admin.get('/index', (req, res) => {
res.send('欢迎页');
});

module.exports = admin;

使用上面的模块

// app.js
const home = require('./route/home.js');
const admin = require('./route/admin.js');

// 把二级路由和遗迹路由绑定起来
app.use('/home' , home);
app.use('/admin' , admin);

GET参数获取

Express框架中使用 req.query 即可获取GET参数,框架内部会将GET参数转换为对象并返回

const express = require('express');
// 创建网站服务器
const app = express();

// http://localhost:3000/?name=alsritter&gender=male
app.get('/', (req, res) => {
res.send(req.query)
})

console.log('启动服务器');
app.listen(3000)

POST参数获取

Express中接收post请求参数需要借助第三方包 body-parser

这个包也是官方的,但是官方为了缩小体积便将其抽离了出来

所以需要使用 npm 进行安装

npm install body-parser --save
const express = require('express');
const bodyParser = require('body-parser');
// 创建网站服务器
const app = express();

// 配置body-parser模块

// urlencoded:
// true 方法内部使用第三方模块qs处理请求参数的格式
// false 方法内部使用querystring模块处理请求参数的格式
app.use(express.urlencoded({ extended: true })); // 接收 x-www-form-urlencoded 数据
app.use(express.json()); // 接收 JSON 数据

// http://localhost:3000
// 通过postman 发post请求
app.post('/', (req, res) => {
res.send(req.body)
})

console.log('启动服务器');
app.listen(3000)

补:使用 Postman 发post请求需要在 Header 里设置

Key 设置为 Content-Type

value 设置为 application/json

然后再在 Body 里的 x-www-form-urlencoded 里写数据

参考资料 postman 中 form-data、x-www-form-urlencoded 的区别

指定请求参数

指定请求需要哪些参数

: 进行分隔,后面就是请求参数 传输数据也无需使用 ? 分隔,而是直接跟在后面,类似java的 RestFul 风格请求资源 接收数据使用的是 params

例:

const express = require('express');
// 创建网站服务器
const app = express();

// 以 : 进行分隔,后面就是请求参数
// 传输数据也无需使用 ? 分隔
// http://localhost:3000/find/10
app.get('/find/:id', (req, res) => {
// 接收数据使用的不是query而是params
res.send(req.params)
})

console.log('启动服务器');
app.listen(3000)

可以写多个参数

// http://localhost:3000/find/123/alsritter/20

app.get('/find/:id/:name/:age', (req, res) => {
res.send(req.params)
})

静态资源处理

通过 Express 内置的 express.static 可以方便的托管静态文件,例如img、css、JavaScript 文件等

express.static 就是指定一个静态资源的根目录,能直接访问到这个目录下的所有文件

const express = require('express');
// 引入路径处理包来拼接路径
const path = require('path')
// 创建网站服务器
const app = express();

// 实现静态资源访问功能
// http://localhost:3000/temp.txt 就能访问到 public/temp.txt 文件
app.use(express.static(path.join(__dirname,'public')))

console.log('启动服务器');
app.listen(3000)

还可以在静态资源路径前面再加一个路径(使用的是 use 自身的路径匹配)

// http://localhost:3000/static/temp.txt 就能访问到 public/temp.txt 文件
app.use('/static',express.static(path.join(__dirname,'public')))

响应数据

返回一个 JSON

直接使用 res.json() 就行了,这个方法会自动设置响应头

HTTP访问控制

参考资料

参考资料 MDN--HTTP访问控制(CORS) 参考资料 MDN--同源策略 参考资料 Fundebug--九种跨域方式实现原理 参考资料 阮一峰--浏览器同源政策及其规避方法 参考资料 NathanYangcn--跨域之二:JSONP 和 CORS

配置 CORS

Express 代码如下:

// 设置允许跨域访问该服务
app.all('*', function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Headers', 'mytoken');
next();
});

方法一:修改请求头 对应 Cors

在3001服务器的请求响应头里加入 Access-Control-Allow-Origin

// 3001端口的服务 返回数据
let app2 = express();
app2.get('/', (rep, res) => {
res.header("Access-Control-Allow-Origin", "*");
res.send("你好")
})
app2.listen(3001)

方法二:传参函数名拼接调用 对应 jsonp

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<h1>返回测试页面</h1>
<script>
function f(data){
alert(data)
}
</script>
<!-- 请求后台3001服务器时传入一个参数callback=f 注意这个 f 是前面定义的函数名 -->
<script src="http://localhost:3001?callback=f"></script>
</body>

</html>
const express = require('express');

// 3000端口的服务 将当前目录作为http服务
let app = express();
// 注意,当静态目录下有 index.html 默认将这个html返回出去
app.use(express.static(__dirname))
app.listen(3000)

// 3001端口的服务 返回数据
let app2 = express();
app2.get('/', (rep, res) => {

// 后台取得前台传入的那个参数
let fun = rep.query.callback;
// 把前台传过来的参数再拼接成一个函数传回去(就是拼接成一个函数调用)
res.send(fun + "('你好')");
})
app2.listen(3001)

就可以加载到数据了

CORS 的使用

参考资料 Express--cors 参考资料 MDN--HTTP访问控制(CORS)

首先安装

npm install cors --save

然后只需要在api服务端加入一个

app2.use(cors())

就可以了,如下

const express = require('express');
const cors = require('cors');

// 3000端口的服务 将当前目录作为http服务
let app = express();
// 注意,当静态目录下有 index.html 默认将这个html返回出去
app.use(express.static(__dirname))
app.listen(3000)


// 3001端口的服务 返回数据
let app2 = express();

app2.use(cors())
app2.get('/', (rep, res) => {
res.send("你好");
})
app2.listen(3001)

上面的 cors() 默认配置如下

{
"origin": "*",
"methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
"preflightContinue": false,
"optionsSuccessStatus": 204
}

express 跨域模板

如果不使用 cors 则是通过 header 来处理的 如下

express 跨域是直接通过设置header属性来完成的

  • Access-Control-Allow-Origin 允许的域
  • Access-Control-Allow-Headers 允许的header类型
  • Access-Control-Allow-Methods 允许的Methods类型
// 设置允许跨域访问该服务
app.all('*', function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
//
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Headers', 'mytoken');
next();
});

express-art-template 模板引擎

模板引擎 🌟

就是类似Vue的那样对数据进行绑定

参考资料 前端模版引擎---art-template-【下】 里面介绍了一些art-template语法

为了使 art-template 模板引擎能够更好的和 Express 框架配合,模板引擎官方在原 art-template 模板引擎的基础上再次封装了 express-art-template

所以安装时需要把两个都安装上

npm install art-template express-art-template --save
const express = require('express');
// 引入路径处理包来拼接路径
const path = require('path')
// 创建网站服务器
const app = express();

// 当渲染后缀为art的模板时 使用 express-art-template 引擎
app.engine('art', require('express-art-template'));

// 设置模板存放目录
// 第一个参数是固定的
app.set('views', path.join(__dirname, 'views'));

// 设置express框架默认后缀为 art
app.set('view engine', 'art');

app.get('/', (req, res) => {
// 渲染模板:就像Vue那样,把数据传给 index.art 这个文件的 {{ msg }} 里·
// 1. 拼接模板路径
// 2. 拼接模板后缀
// 3. 哪个模板和哪个数据进行绑定
// 4. 将拼接结果响应给客户端
res.render('index', {
name: 'alsritter',
gender: 'male',
age: 20
});
})

console.log('启动服务器');
app.listen(3000)

index.art 文件的内容

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<h1>{{name}}</h1>
<h1>{{gender}}</h1>
<h1>{{age}}</h1>
</body>

</html>

app.locals 对象

将变量设置到 app.locals 对象下面,这个数据在所以的模板中都可以获取到

app.locals.users = [
{name: '张三',age: 20},
{name: '李四',age: 18}
]